#include "StdAfx.h"
/// Copyright 2012, Alex Averian, Yonan Research
/// Released under BSD Licence

#include "qarchive.h"
using namespace micro::files;



QArchive::QArchive( QFile* pFile, Mode mode,  int nBufSize/* = 4096*/, BYTE* lpBuf /*= nullptr*/ )
{
    ASSERT( pFile );
    m_pFile = pFile;
    m_modeArchive = mode;
    
    m_nBufSize = nBufSize;
    
    if ( lpBuf )
    {
        m_bUserBuf = true;
        m_lpBufStart = lpBuf;
    }
    else
    {
        m_bUserBuf = false;
        m_lpBufStart = new BYTE[m_nBufSize];
    }
    
    m_lpBufMax = m_lpBufStart + m_nBufSize;
    
    m_lpBufCur = ( IsLoading() ) ? m_lpBufMax : m_lpBufStart;
    
}

QArchive::~QArchive()
{
    Flush();
    
    if ( false == m_bUserBuf )
        delete [] m_lpBufStart;
        
    m_pFile = nullptr;
}

QArchive& QArchive::operator<<( int i )
{
    return QArchive::operator<<( ( LONG )i );
}
QArchive& QArchive::operator<<( unsigned int u )
{
    return QArchive::operator<<( ( LONG )u );
}
QArchive& QArchive::operator<<( short w )
{
    return QArchive::operator<<( ( WORD )w );
}
QArchive& QArchive::operator<<( char ch )
{
    return QArchive::operator<<( ( BYTE )ch );
}
QArchive& QArchive::operator<<( wchar_t ch )
{
    return QArchive::operator<<( ( WORD )ch );
}
QArchive& QArchive::operator<<( bool b )
{
    return QArchive::operator <<( ( BYTE )( b ? 1 : 0 ) );
}

QArchive& QArchive::operator<<( BYTE by )
{
    if ( m_lpBufCur + sizeof( BYTE ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED BYTE* )m_lpBufCur = by;
    m_lpBufCur += sizeof( BYTE );
    return *this;
}


QArchive& QArchive::operator<<( LONGLONG dwdw )
{
    if ( m_lpBufCur + sizeof( LONGLONG ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED LONGLONG* )m_lpBufCur = dwdw;
    m_lpBufCur += sizeof( LONGLONG );
    return *this;
}

QArchive& QArchive::operator<<( ULONGLONG dwdw )
{
    if ( m_lpBufCur + sizeof( ULONGLONG ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED ULONGLONG* )m_lpBufCur = dwdw;
    m_lpBufCur += sizeof( ULONGLONG );
    return *this;
}

QArchive& QArchive::operator<<( WORD w )
{
    if ( m_lpBufCur + sizeof( WORD ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED WORD* )m_lpBufCur = w;
    
    m_lpBufCur += sizeof( WORD );
    return *this;
}

QArchive& QArchive::operator<<( LONG l )
{
    if ( m_lpBufCur + sizeof( LONG ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED LONG* )m_lpBufCur = l;
    m_lpBufCur += sizeof( LONG );
    return *this;
}


QArchive& QArchive::operator<<( DWORD dw )
{
    if ( m_lpBufCur + sizeof( DWORD ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED DWORD* )m_lpBufCur = dw;
    m_lpBufCur += sizeof( DWORD );
    return *this;
}

QArchive& QArchive::operator<<( float f )
{
    if ( m_lpBufCur + sizeof( float ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED float* )m_lpBufCur = f;
    m_lpBufCur += sizeof( float );
    return *this;
}

QArchive& QArchive::operator<<( double d )
{
    if ( m_lpBufCur + sizeof( double ) > m_lpBufMax )
        Flush();
        
    *( UNALIGNED double* )m_lpBufCur = d;
    m_lpBufCur += sizeof( double );
    return *this;
}

QArchive& QArchive::operator>>( int& i )
{
    return QArchive::operator>>( ( LONG& )i );
}
QArchive& QArchive::operator>>( unsigned int& u )
{
    return QArchive::operator>>( ( LONG& )u );
}
QArchive& QArchive::operator>>( short& w )
{
    return QArchive::operator>>( ( WORD& )w );
}
QArchive& QArchive::operator>>( char& ch )
{
    return QArchive::operator>>( ( BYTE& )ch );
}

QArchive& QArchive::operator>>( wchar_t& ch )
{
    return QArchive::operator>>( ( WORD& )ch );
}

QArchive& QArchive::operator>>( bool& b )
{
    BYTE by;
    QArchive& ar = QArchive::operator>>( by );
    b = ( by ? true : false );
    return ar;
}


QArchive& QArchive::operator>>( BYTE& by )
{
    if ( m_lpBufCur + sizeof( BYTE ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( BYTE ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    by = *( UNALIGNED BYTE* )m_lpBufCur;
    m_lpBufCur += sizeof( BYTE );
    return *this;
}

QArchive& QArchive::operator>>( LONGLONG& dwdw )
{
    if ( m_lpBufCur + sizeof( LONGLONG ) > m_lpBufMax )
        FillBuffer( sizeof( LONGLONG ) - ( UINT )( m_lpBufMax - m_lpBufCur ) );
        
    dwdw = *( UNALIGNED LONGLONG* )m_lpBufCur;
    m_lpBufCur += sizeof( LONGLONG );
    return *this;
}

QArchive& QArchive::operator>>( ULONGLONG& dwdw )
{
    if ( m_lpBufCur + sizeof( ULONGLONG ) > m_lpBufMax )
        FillBuffer( sizeof( ULONGLONG ) - ( UINT )( m_lpBufMax - m_lpBufCur ) );
        
    dwdw = *( UNALIGNED ULONGLONG* )m_lpBufCur;
    m_lpBufCur += sizeof( ULONGLONG );
    return *this;
}

QArchive& QArchive::operator>>( WORD& w )
{
    if ( m_lpBufCur + sizeof( WORD ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( WORD ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    w = *( UNALIGNED WORD* )m_lpBufCur;
    m_lpBufCur += sizeof( WORD );
    return *this;
}

QArchive& QArchive::operator>>( DWORD& dw )
{
    if ( m_lpBufCur + sizeof( DWORD ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( DWORD ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    dw = *( UNALIGNED DWORD* )m_lpBufCur;
    m_lpBufCur += sizeof( DWORD );
    return *this;
}

QArchive& QArchive::operator>>( float& f )
{
    if ( m_lpBufCur + sizeof( float ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( float ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    f = *( UNALIGNED float* )m_lpBufCur;
    m_lpBufCur += sizeof( float );
    return *this;
}

QArchive& QArchive::operator>>( double& d )
{
    if ( m_lpBufCur + sizeof( double ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( double ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    d = *( UNALIGNED double* )m_lpBufCur;
    m_lpBufCur += sizeof( double );
    return *this;
}

QArchive& QArchive::operator>>( LONG& l )
{
    if ( m_lpBufCur + sizeof( LONG ) > m_lpBufMax )
        FillBuffer( UINT( sizeof( LONG ) - ( m_lpBufMax - m_lpBufCur ) ) );
        
    l = *( UNALIGNED LONG* )m_lpBufCur;
    m_lpBufCur += sizeof( LONG );
    return *this;
}

QArchive& QArchive::operator<<(const QString& str )
{
    int len = str.GetByteSize();
    *this << len;
    
    Write( ( const void* )( LPCTSTR )str, len );
    return *this;
}

QArchive& QArchive::operator>>( QString& str )
{
    int len = 0;
    *this >> len;
    
    size_t readed = Read( ( void* ) str.GetBuffer( len ), len );
    return *this;
}

void QArchive::FillBuffer( UINT nBytesNeeded )
{

    UINT nUnused = ( UINT )( m_lpBufMax - m_lpBufCur );
    ULONG nTotalNeeded = ( ( ULONG )nBytesNeeded ) + nUnused;
    
    // Fill up the current buffer from file
    
    ASSERT( m_lpBufCur  );
    ASSERT( m_lpBufStart  );
    ASSERT( m_lpBufMax  );
    
    if ( m_lpBufCur > m_lpBufStart )
    {
        // Copy unused
        if ( ( int )nUnused > 0 )
        {
            memmove( m_lpBufStart, m_lpBufCur, nUnused );
            m_lpBufCur = m_lpBufStart;
            m_lpBufMax = m_lpBufStart + nUnused;
        }
        
        // Read to satisfy nBytesNeeded or nLeft if possible
        size_t nRead = nUnused;
        size_t nLeft = 0;
        size_t nBytes = 0;
        
        // Only read what we have to, to avoid blocking waiting on data
        // we don't need
        
        nLeft = m_nBufSize - nUnused;
        BYTE* lpTemp = m_lpBufStart + nUnused;
        
        do
        {
            nBytes = m_pFile->Read( lpTemp, nLeft );
            lpTemp = lpTemp + nBytes;
            nRead += nBytes;
            nLeft -= nBytes;
        }
        while ( nBytes > 0 && nLeft > 0 && nRead < nBytesNeeded );
        
        m_lpBufCur = m_lpBufStart;
        m_lpBufMax = m_lpBufStart + nRead;
    }
    
    
    // Not enough data to fill request?
    if ( ( ULONG )( m_lpBufMax - m_lpBufCur ) < nTotalNeeded )
    {
    
        throw QError( L"load error!" );
    }
}

size_t QArchive::Read( void* lpBuf, size_t nMax )
{
    ASSERT( IsLoading() );
    
    // Try to fill from buffer first
    size_t nMaxTemp = nMax;
    size_t nTemp = min( nMaxTemp, ( UINT )( m_lpBufMax - m_lpBufCur ) );
    
    if ( nTemp > 0 )
    {
        memcpy( lpBuf, m_lpBufCur, nTemp );
        m_lpBufCur += nTemp;
        lpBuf = ( BYTE* )lpBuf + nTemp;
        nMaxTemp -= nTemp;
    }
    
    if ( nMaxTemp > 0 )
    {
        ASSERT( m_lpBufCur == m_lpBufMax );
        size_t nBytes = m_pFile->Read( lpBuf, nMaxTemp );
    }
    
    return nMax;
}

void QArchive::Write( const void* lpBuf, UINT nMax )
{
    ASSERT( IsStoring() );
    
    ASSERT( lpBuf );
    
    if ( nMax == 0 )
        return;
        
    // Copy to buffer if possible
    UINT nTemp = min( nMax, ( UINT )( m_lpBufMax - m_lpBufCur ) );
    
    if ( nTemp > 0 )
    {
        memcpy( m_lpBufCur, lpBuf, nTemp );
        m_lpBufCur += nTemp;
        lpBuf = ( BYTE* )lpBuf + nTemp;
        nMax -= nTemp;
    }
    
    if ( nMax > 0 )
    {
        Flush();    // flush the full buffer
        ASSERT( m_lpBufCur == m_lpBufStart );
        
        m_pFile->Write( lpBuf, nMax );
    }
}


void QArchive::Flush()
{
    // Write out the current buffer to file
    if ( m_lpBufCur != m_lpBufStart )
        m_pFile->Write( m_lpBufStart, ULONG( m_lpBufCur - m_lpBufStart ) );
        
    m_lpBufCur = m_lpBufStart;
}


void QArchive::Close()
{
    Flush();
    m_pFile = nullptr;
}

void QArchive::Abort()
{
    m_pFile = nullptr;
    
}   // close and shutdown without exceptions


void QArchive::WriteCount( DWORD dwCount )
{
    if ( dwCount < 0xFFFF )
        *this << ( WORD )dwCount;
    else
    {
        *this << ( WORD )0xFFFF;
        *this << dwCount;
    }
}

DWORD QArchive::ReadCount()
{
    WORD wCount;
    *this >> wCount;
    
    if ( wCount != 0xFFFF )
        return wCount;
        
    DWORD dwCount;
    *this >> dwCount;
    return dwCount;
}


void QArchive::SetStoreParams( UINT nHashSize, UINT nBlockSize )
{
    ASSERT( IsStoring() );
    //ASSERT(m_pStoreMap == nullptr);    // must be before first object written
    m_nHashSize = nHashSize;
    m_nGrowSize = nBlockSize;
}

void QArchive::SetLoadParams( UINT nGrowBy )
{
    ASSERT( IsLoading() );
    //ASSERT(m_pLoadArray == nullptr);   // must be before first object read
    m_nGrowSize = nGrowBy;
}

